/*
 * 
 * v1.2 by tonydon
 */
package cn.txdnet.txweb.annotation.validation;

import cn.txdnet.txweb.utils.Constants;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Iterator;
import txdnet.util.DataUtil;
import txdnet.util.FieldUtil;
import txdnet.util.Logger;

/**
 * 匿名标准POJO对象 验证类
 *
 * @author txdnet
 */
public class Validator {

    public static boolean validate(Object obj) {
        boolean flag = true;
        try {
            if (obj == null) {
                throw new RuntimeException("被验证对象不能为空");
            }
            validate(obj, "obj");
        } catch (RuntimeException re) {
            Logger.error(Validator.class, re.toString());
            flag = false;
        }
        return flag;
    }

    /*
     * 验证Object字段
     *
     */
    private static void validateFields(Object obj) {
        Field[] fields = obj.getClass().getDeclaredFields();
        if (fields != null && fields.length > 0) {
            for (Field f : fields) {
                f.setAccessible(true);
                Annotation[] annotations = f.getDeclaredAnnotations();
                if (annotations.length != 0) {
                    validateFieldByAnnotations(obj, f);
                }

                Object value = getValue(f, obj);

                // should always check null
                if (value != null) {
                    validate(value, f.getName());
                }
            }
        }
    }

    /*
     * 基本对象不参与验证,集合、数组和POJO分别验证 递归集合验证
     */
    private static void validate(Object obj, String fieldName) {
        if (!requireValidate(obj)) {
            return;
        }

        if (obj instanceof Collection) {
            validateCollectionElements(obj, fieldName);
            return;
        }

        if (obj.getClass().isArray()) {
            validateArrayItems(obj, fieldName);
            return;
        }

        validateFields(obj);
    }

    /*
     * 验证集合中的每一个POJO对象
     */
    private static void validateCollectionElements(Object obj,
            String fieldName) {

        Iterator<?> it = ((Collection<?>) obj).iterator();
        while (it.hasNext()) {
            Object item = it.next();
            if (item == null) {
                throw new RuntimeException(
                        String.format("%s.%s 集合元素不能为空 Collection Items Exist Null",
                        obj.getClass().getCanonicalName(),
                        fieldName));
            }
            validate(item);
        }
    }

    /*
     * 验证数组中的每一个POJO对象
     */
    private static void validateArrayItems(Object obj, String fieldName) {
        int arrayLength = Array.getLength(obj);
        for (int i = 0; i < arrayLength; i++) {
            Object item = Array.get(obj, i);
            if (item == null) {
                throw new RuntimeException(
                        String.format("%s.%s 数组元素不能为空 Array Items Exist Null",
                        obj.getClass().getCanonicalName(),
                        fieldName));
            }
            validate(item);
        }
    }

    /*
     * 通过匿名标注验证字段
     */
    private static void validateFieldByAnnotations(Object obj, Field f) {
        
        f.setAccessible(true);

        if (f.isAnnotationPresent(NotEmpty.class)) {
            handleNotEmpty(f, obj);
        }

        if (f.isAnnotationPresent(ClearXss.class)) {
            handleClearXss(f, obj);
        }
        
        if (f.isAnnotationPresent(ClearEnterBlank.class)) {
            handleClearEnterBlank(f, obj);
        }

        if (f.isAnnotationPresent(Size.class)) {
            handleSize(f, obj);
        }

        if (f.isAnnotationPresent(Range.class)) {
            handleRange(f, obj);
        }

        if (f.isAnnotationPresent(Match.class)) {
            handleMatch(f, obj);
        }

        if (f.isAnnotationPresent(Default.class)) {
            handleDefault(f, obj);
        }

        if (f.isAnnotationPresent(CurrTime.class)) {
            handleCurrTime(f, obj);
        }
    }

    /*
     * 判断是否为可以验证的类型 @param Object @return boolean
     */
    private static boolean requireValidate(Object obj) {
        Class<?> type = obj.getClass();

        return !(type.isPrimitive() || type.isEnum()
                || obj instanceof String || obj instanceof Number
                || obj instanceof Boolean || obj instanceof Byte || obj instanceof Charset);
    }

    /**
     * 只处理数字类型
     *
     * @param f
     * @param obj
     */
    private static void handleRange(Field f, Object obj) {
        Object value = getValue(f, obj);
        if (value == null) {
            throw new RuntimeException(
                    String.format("%s.%s 字段不能为空 ",
                    obj.getClass().getCanonicalName(),
                    f.getName()));
        }

        Range range = f.getAnnotation(Range.class);
        if (value instanceof Byte) {
            if ((Byte) value < range.min() || (Byte) value > range.max()) {
                throw new RuntimeException(
                        String.format("%s.%s 字段值(range)不符合范围要求 ",
                        obj.getClass().getCanonicalName(),
                        f.getName()));
            }
            return;
        }

        if (value instanceof Short) {
            if ((Short) value < range.min() || (Integer) value > range.max()) {
                throw new RuntimeException(
                        String.format("%s.%s 字段值(range)不符合范围要求 ",
                        obj.getClass().getCanonicalName(),
                        f.getName()));
            }
            return;
        }

        if (value instanceof Integer) {
            if ((Integer) value < range.min() || (Integer) value > range.max()) {
                throw new RuntimeException(
                        String.format("%s.%s 字段值(range)不符合范围要求 ",
                        obj.getClass().getCanonicalName(),
                        f.getName()));
            }
            return;
        }

        if (value instanceof Long) {
            if ((Long) value < range.min() || (Long) value > range.max()) {
                throw new RuntimeException(
                        String.format("%s.%s 字段值(range)不符合范围要求 ",
                        obj.getClass().getCanonicalName(),
                        f.getName()));
            }
            return;
        }

        if (value instanceof Float) {
            if (((Float) value).compareTo((float) range.min()) < 0
                    || ((Float) value).compareTo((float) range.max()) > 0) {
                throw new RuntimeException(
                        String.format("%s.%s 字段值(range)不符合范围要求 ",
                        obj.getClass().getCanonicalName(),
                        f.getName()));
            }
            return;
        }

        if (value instanceof Double) {
            if (((Double) value).compareTo((Double) range.min()) < 0
                    || ((Double) value).compareTo((Double) range.max()) > 0) {
                throw new RuntimeException(
                        String.format("%s.%s 字段值(range)不符合范围要求 ",
                        obj.getClass().getCanonicalName(),
                        f.getName()));
            }
            return;
        }

        if (value instanceof BigDecimal) {

            BigDecimal decimal = (BigDecimal) value;
            BigDecimal maxDecimal = BigDecimal.valueOf(range.max());
            BigDecimal minDecimal = BigDecimal.valueOf(range.min());

            if (decimal.compareTo(minDecimal) < 0 || decimal.compareTo(maxDecimal) > 0) {
                throw new RuntimeException(
                        String.format("%s.%s 字段值(range)不符合要求 ",
                        obj.getClass().getCanonicalName(),
                        f.getName()));
            }
        }
    }

    /**
     * 验证Object Null 和String Empty
     *
     * @param f
     * @param obj
     */
    private static void handleNotEmpty(Field f, Object obj) {
        Object value = getValue(f, obj);
        if (value == null) {
            throw new RuntimeException(
                    String.format("%s.%s 字段不能为空 ",
                    obj.getClass().getCanonicalName(),
                    f.getName()));
        }

        if (value instanceof String) {
            if (((String) value).trim().isEmpty()) {
                throw new RuntimeException(
                        String.format("%s.%s 字段不能为空 ",
                        obj.getClass().getCanonicalName(),
                        f.getName()));
            }
        }
    }

    /*
     * 处理尺寸大小匿名标注 @param f @param obj
     */
    private static void handleSize(Field f, Object obj) {
        Object value = getValue(f, obj);
        if (value == null) {
            return;
        }

        Size size = f.getAnnotation(Size.class);

        if (size.min() < 0) {
            return;
        }

        int length = 0;
        if (value instanceof String) {
            length = ((String) value).length();
        }

        if (value instanceof Collection) {
            Collection<?> coll = (Collection<?>) value;
            length = coll.size();
        }

        if (value.getClass().isArray()) {
            length = Array.getLength(value);
        }

        if (length < size.min()) {
            throw new RuntimeException(
                    String.format("%s.%s 字段长度或大小(size)小于要求值",
                    obj.getClass().getCanonicalName(),
                    f.getName()));
        }
        if (length > size.max()) {
            throw new RuntimeException(
                    String.format("%s.%s 字段长度或大小(size)大于要求值",
                    obj.getClass().getCanonicalName(),
                    f.getName()));
        }

    }

    /*
     * 处理正则验证String类型字段 @param f @param obj
     */
    private static void handleMatch(Field f, Object obj) {
        Object value = getValue(f, obj);
        if (value == null) {
            return;
        }

        if (!(value instanceof String)) {
            return;
        }

        if (((String) value).length() == 0) {
            return;
        }

        Match match = f.getAnnotation(Match.class);

        if (match.value() == null) {
            return;
        }

        if (match.value() == Match.RegexType.TEL) {
            if (!Constants.RE_TEL.matcher((String) value).matches()) {
                throw new RuntimeException(
                        String.format("%s.%s 手机号码不符合要求: - %s",
                        obj.getClass().getCanonicalName(),
                        f.getName(),
                        value));
            }
            return;
        }
        if (match.value() == Match.RegexType.EMAIL) {
            if (!Constants.RE_EMAIL.matcher((String) value).matches()) {
                throw new RuntimeException(
                        String.format("%s.%s 电子邮箱地址不符合要求: - %s",
                        obj.getClass().getCanonicalName(),
                        f.getName(),
                        value));
            }
            return;
        }
        if (match.value() == Match.RegexType.TITLE) {
            if (!Constants.RE_TITLE.matcher((String) value).matches()) {
                throw new RuntimeException(
                        String.format("%s.%s 标题不符合要求: - %s",
                        obj.getClass().getCanonicalName(),
                        f.getName(),
                        value));
            }
            return;
        }
        if (match.value() == Match.RegexType.NICK) {
            if (!Constants.RE_NICK.matcher((String) value).matches()) {
                throw new RuntimeException(
                        String.format("%s.%s 姓名或昵称不符合要求: - %s",
                        obj.getClass().getCanonicalName(),
                        f.getName(),
                        value));
            }
        }


    }

    /**
     * xss html标签字符过滤
     *
     * @param f
     * @param obj
     */
    private static void handleClearXss(Field f, Object obj) {
        Object value = getValue(f, obj);
        if (value == null) {
            return;
        }

        if (value instanceof String) {
            String out_value = DataUtil.parseTxt2Html((String) value);
            try {
                f.set(obj, out_value);
            } catch (Exception e) {
                Logger.error(Validator.class,
                        String.format("%s.%s 设置(ClearXss)字段新对象异常: %s",
                        obj.getClass().getCanonicalName(), f.getName(), e.toString()));

                throw new RuntimeException(
                        String.format("%s.%s 更新(ClearXss)字段值异常.",
                        obj.getClass().getCanonicalName(),
                        f.getName()));

            }
        }
    }
    
    /**
     *  清除\r\n\t\b\f字符
     * @param f
     * @param obj 
     */
    private static void handleClearEnterBlank(Field f, Object obj){
        Object value = getValue(f, obj);
        if (value == null) {
            return;
        }
         if (value instanceof String) {
            String out_value = DataUtil.clearNonBlankChar((String) value);
            try {
                f.set(obj, out_value);
            } catch (Exception e) {
                Logger.error(Validator.class,
                        String.format("%s.%s 设置(ClearEnterBlank)字段新对象异常: %s",
                        obj.getClass().getCanonicalName(), f.getName(), e.toString()));

                throw new RuntimeException(
                        String.format("%s.%s 更新(ClearEnterBlank)字段值异常.",
                        obj.getClass().getCanonicalName(),
                        f.getName()));

            }
        }
    }

    /*
     * 设置缺省值 @param f @param obj
     */
    private static void handleDefault(Field f, Object obj) {
        Object value = getValue(f, obj);
        Default def = f.getAnnotation(Default.class);
        Class type = FieldUtil.getBoxClass(f.getType());
        try {
            if (type == String.class && (value == null || ((String) value).isEmpty())) {
                f.set(obj, def.value());
            } else if ((type == Byte.class) && (value == null || ((Byte) value).byteValue() == 0)) {
                f.set(obj, def.byteVal());
            } else if ((type == Integer.class) && (value == null || ((Integer) value).intValue() == 0)) {
                f.set(obj, def.intVal());
            } else if ((type == Long.class) && (value == null || ((Long) value).longValue() == 0)) {
                f.set(obj, def.longVal());
            } else if ((type == Float.class) && (value == null || ((Float) value).floatValue() == 0)) {
                f.set(obj, def.floatVal());
            } else if ((type == Double.class) && (value == null || ((Double) value).doubleValue() == 0)) {
                f.set(obj, def.doubleVal());
            }
        } catch (Exception ex) {
            Logger.error(Validator.class,
                    String.format("handleDefault() Exception: %s", ex.toString()));
        }
    }

    /**
     * 设置当前字段值为系统毫秒时间
     *
     * @param f
     * @param obj
     */
    private static void handleCurrTime(Field f, Object obj) {
        Object value = getValue(f, obj);
        Class clazz = FieldUtil.getBoxClass( f.getType() );
        try {
            if ((clazz == Long.class) && (value == null || ((Long) value).longValue() == 0)) {
                f.set(obj, System.currentTimeMillis());
            }
        } catch (Exception ex) {
            Logger.error(Validator.class,
                    String.format("handleCurrTime() Exception: %s", ex.toString()));
        }

    }

    /*
     * 得到对象字段值 @param f @param obj @return Object
     */
    private static Object getValue(Field f, Object obj) {
        try {
            return f.get(obj);
        } catch (Exception ex) {
            Logger.error(Validator.class,
                    String.format("getValue() Exception: %s", ex.toString()));
            return null;
        }
    }
}